package server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;

import player.Player;
import player.PlayerEdit;
import player.PlayerManager;
import server.util.*;
import adventure.Adventure;
import adventure.AdventureEdit;
import adventure.AdventureInput;
import colour.ColourUtil;
import java.net.InetAddress;
import java.util.ArrayList;

/**
 *
 * @author Michael Hanns
 *
 */
public class NetworkClientThreadImp extends Thread implements NetworkClientThread {

    private NetworkServer parent;
    private Socket playerSocket;
    private BufferedReader in;
    private PrintWriter out;
    private boolean playerConnected;
    private boolean playerLoggedOn;
    private Adventure adv;
    private PlayerManager players;
    private LoginManager login;
    private AdventureInput advInp;
    private String motd;
    private Broadcaster broadcaster;
    private String serverGreeting;

    public NetworkClientThreadImp(NetworkServer parent, Socket s, AdventureInput advInp, PlayerManager p,
            Broadcaster b, String motd, String greeting, int capacity, double serverVersion) {
        this.parent = parent;
        this.playerSocket = s;
        try {
            this.playerSocket.setSoTimeout(1800000);
        } catch (SocketException e) {
            e.printStackTrace();
            disconnect(true);
        }
        this.advInp = advInp;
        this.adv = advInp.getAdventure();
        this.players = p;

        this.serverGreeting = greeting;

        this.motd = motd;
        this.playerConnected = false;
        this.playerLoggedOn = false;
        this.broadcaster = b;

        try {
            this.in = new BufferedReader(new InputStreamReader(playerSocket.getInputStream()));
            this.out = new PrintWriter(playerSocket.getOutputStream(), true);

            this.login = new LoginManagerImp(in, out, players, capacity);

            playerConnected = true;
        } catch (IOException e) {
            disconnect(false);
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        out.println("\n" + serverGreeting.replace("\n", "\r\n"));

        while (playerConnected && !login.loggedOn()) {
            if (!login.loginPrompt()) {
                disconnect(true);
            }
        }

        if (!playerConnected) {
            return;
        }

        this.playerLoggedOn = true;

        broadcaster.addConnection(login.getCharacter(), out);

        // Ensure character is not in an empty room..
        int x = login.getCharacter().x();
        int y = login.getCharacter().y();
        int z = login.getCharacter().z();
        if (((AdventureEdit) adv).getRoomManager().roomExists(x, y, z)) {
            enterGame();
        } else {
            fixBrokenSpawnLocation();
        }

    }

    private void enterGame() {
        broadcaster.broadcastLocalToPlayer(login.getCharacter(),
                ColourUtil.colourise(login.getCharacter().getAliveName(), ColourUtil.player) + " has logged on.");
        out.println(motd.replace("\n", "\r\n"));
        out.println(processCommand("LOOK"));
        takeInput();
    }

    private void fixBrokenSpawnLocation() {
        out.println("It appears you're standing in empty space. Please be patient while WITNA Server finds you somewhere to spawn...\r\n");
        boolean found = false;
        int sx = 0;
        int sy = 0;
        int sz = 0;
        for (int z = 0; z < 100 & !found; z++) {
            for (int y = 0; y < 100 & !found; y++) {
                for (int x = 0; x < 100 & !found; x++) {
                    if (((AdventureEdit) adv).getRoomManager().roomExists(x, y, z)) {
                        found = true;
                        sx = x;
                        sy = y;
                        sz = z;
                    }
                }
            }
        }
        if (found) {
            ((PlayerEdit) login.getCharacter()).setX(sx);
            ((PlayerEdit) login.getCharacter()).setY(sy);
            ((PlayerEdit) login.getCharacter()).setZ(sz);
            enterGame();
        } else {
            out.println("The module this server is running appears to be entirely empty space. Please contact the server administrator for assistance.\r\n");
            disconnect(true);
        }
    }

    private void takeInput() {
        while (playerConnected) {
            try {
                String input = FixTelnetInput.fix(in.readLine());

                if (input != null) {

                    String[] commands = input.split(";");

                    if (commands != null) {
                        for (int c = 0; c < commands.length; c++) {
                            if (input.equalsIgnoreCase("DISCONNECT")
                                    || input.equalsIgnoreCase("QUIT")) {
                                disconnect(true);
                            } else {
                                String output = processCommand(commands[c]);
                                out.println(output);
                            }
                        }
                    }
                }
            } catch (IOException e) {
                disconnect(false);
            } catch (NullPointerException e) {
                disconnect(false);
            }
        }
    }

    private String processCommand(String input) {
        String[] tokens = input.split(" ");
        if (tokens.length > 0 && tokens[0].equalsIgnoreCase("whois")) {
            if (tokens.length > 1) {
                return whois(input.substring(tokens[0].length() + 1)).replace("\n", "\r\n");
            } else {
                return whois().replace("\n", "\r\n");
            }
        } else if (tokens.length > 0 && login.getCharacter().isAdmin() && tokens[0].equalsIgnoreCase("kick")) {
            if (tokens.length > 2) {
                return parent.kick(tokens[1], input.substring(tokens[0].length() + tokens[1].length() + 2)).replace("\n", "\r\n");
            } else if (tokens.length > 1) {
                return parent.kick(tokens[1], "").replace("\n", "\r\n");
            } else {
                return "Kick whom [for what reason]?\r\nSyntax: kick <playername> [<reason>]";
            }
        } else if (tokens.length > 0 && login.getCharacter().isAdmin() && tokens[0].equalsIgnoreCase("ban")) {
            if (tokens.length > 2) {
                return parent.ban(tokens[1], input.substring(tokens[0].length() + tokens[1].length() + 2)).replace("\n", "\r\n");
            } else if (tokens.length > 1) {
                return parent.ban(tokens[1], "").replace("\n", "\r\n");
            } else {
                return "Ban whom [for what reason]?\r\nSyntax: Ban <playername> [<reason>]";
            }
        } else {
            String output = Utilities.wordWrap(advInp.processCommand(login.getCharacter(), input));

            output = output.replace("\n", "\r\n");

            return output;
        }
    }

    private String whois(String playerName) {
        for (int x = 0; x < players.onlinePlayers(); x++) {
            if (players.getOnlinePlayerAtIndex(x).getName().equalsIgnoreCase(playerName)) {
                return players.getOnlinePlayerAtIndex(x).getStatusTable();
            }
        }
        return "No player " + playerName + " is currently connected.";
    }

    private String whois() {
        String[] playersConnected = new String[players.onlinePlayers() + 4];
        playersConnected[0] =
                "+------------------------------------------------------------------------------+";
        playersConnected[1] =
                "| Name         | Lvl | Vocation     | Description                              |";
        playersConnected[2] =
                "|--------------+-----+--------------+------------------------------------------|";
        for (int x = 0; x < players.onlinePlayers(); x++) {
            Player player = players.getOnlinePlayerAtIndex(x);
            // Put the name on
            playersConnected[3 + x] = "| " + player.getName();

            // Fill blank space
            for (int y = 0; y < 12 - player.getName().length(); y++) {
                playersConnected[3 + x] += " ";
            }

            // Put the level on
            playersConnected[3 + x] += " | " + player.getLevel();

            // Fill blank space
            if (player.getLevel() < 10) {
                playersConnected[3 + x] += "  ";
            } else if (player.getLevel() < 100) {
                playersConnected[3 + x] += " ";
            }

            // Put the vocation on
            playersConnected[3 + x] += " | " + player.getVocationName();

            // Fill blank space
            for (int y = 0; y < 12 - player.getVocationName().length(); y++) {
                playersConnected[3 + x] += " ";
            }

            // Put the description on
            if (player.getDescription().length() > 40) {
                // Trail off if it's too long
                String description = " | " + player.getDescription().substring(0, 37) + "...";
                playersConnected[3 + x] += description;
            } else {
                // Fill blank space
                String desc = player.getDescription();
                while(desc.length() < 40) {
                    desc += " ";
                }
                playersConnected[3 + x] += " | " + desc;
            }

            // End the line
            playersConnected[3 + x] += " |";
            
        }
        playersConnected[playersConnected.length - 1] =
                "+------------------------------------------------------------------------------+";

        String output = playersConnected[0];
        for (int x = 1; x < playersConnected.length; x++) {
            output = output.concat("\n".concat(playersConnected[x]));
        }
        return output;
    }
    
    @Override
    public String onlinePlayer() {
        return login.getCharacter().getAliveName();
    }
    
    @Override
    public String getIP() {
        return playerSocket.getInetAddress().getHostAddress();
    }

    @Override
    public void disconnect(boolean serverDisconnect) {
        try {
            if (playerLoggedOn) {
                advInp.leaveGame(login.getCharacter());
                players.logOff(login.getCharacter().getID());
            }

            if (serverDisconnect) {
                out.println("You are now disconnected. Thanks for playing "
                        + adv.getName() + ".");
            }
            playerSocket.close();
            playerConnected = false;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean disconnected() {
        try {
            if (playerSocket.isInputShutdown()) {
                playerSocket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return playerSocket.isClosed();
    }
}